﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using DomainDriver.CommonUtilities.Reflectors;
using DomainDriver.DomainModeling.Constraints;
using DomainDriver.DomainModeling.DataProviders;
using DomainDriver.DomainModeling.DomainModels;
using DomainDriver.DomainModeling.DomainObjects;
using DomainDriver.DomainModeling.Exceptions;
using DomainDriver.DomainModeling.Queries;
using DomainDriver.DomainModeling.Repositories;

namespace DomainDriver.DomainModeling.GenericUnitTesting.Repositories
{
    // NOTE: Add "[TestFixture]" to sub-classes in actual test assembly

    public abstract class RepositoryTestFixture<DOMAIN_MODEL, KEY, KEYED_DOMAIN_OBJECT> : DomainModelTestFixture<DOMAIN_MODEL>
        where DOMAIN_MODEL : class, IDomainModel
        where KEYED_DOMAIN_OBJECT : IKeyedDomainObject<KEY, KEYED_DOMAIN_OBJECT>
    {
        private int m_InitialRepositoryCount = 0;
        private int m_NumberOfTestInstances = 10;

        public RepositoryTestFixture(DataSourcePersistenceType persistenceType)
            : base(persistenceType, false)
        { }

        public int InitialRepositoryCount
        {
            get { return m_InitialRepositoryCount; }
        }

        public int NumberOfTestInstances
        {
            get { return m_NumberOfTestInstances; }
            set
            {
                if (value <= 1)
                {
                    throw new ApplicationException("The number of test instances must be greater than 1.");
                }
                m_NumberOfTestInstances = value;
            }
        }

        protected IRepository<KEY, KEYED_DOMAIN_OBJECT> RepositoryToTest
        {
            get { return DomainModel.GetRepository<KEY, KEYED_DOMAIN_OBJECT>(); }
        }

        protected abstract KEYED_DOMAIN_OBJECT CreateDomainObject();
        protected abstract void UpdateDomainObject(KEYED_DOMAIN_OBJECT domainObject, ref bool objectCanBeUpdated);
        // LATER: protected abstract int GetNumberOfUpdatesInSequence();
        // LATER: protected abstract bool UpdateDomainObjectInSequence(KEYED_DOMAIN_OBJECT domainObject, int updateNumber);

        [SetUp]
        public override void SetUp()
        {
            base.SetUp();

            Assert.AreEqual(0, DomainModel.Constraints.Count);
            m_InitialRepositoryCount = RepositoryToTest.Count();
            if (CreateNewDataStore)
            {
                Assert.AreEqual(0, InitialRepositoryCount);
            }
        }

        [Test]
        public virtual void VerifyRepositoryAfterInitialization()
        {
            KEYED_DOMAIN_OBJECT domainObject = CreateDomainObject();

            Assert.AreEqual(InitialRepositoryCount, RepositoryToTest.Count());
            Assert.AreEqual(InitialRepositoryCount, RepositoryToTest.FindKeys().Count);
            Assert.AreEqual(InitialRepositoryCount, RepositoryToTest.Read().Count);
            Assert.AreEqual(InitialRepositoryCount, RepositoryToTest.Read().Count);
            Assert.IsFalse(RepositoryToTest.Exists(domainObject.Key));
        }

        [Test]
        public virtual void VerifyRepositoryAfterSingleAdd()
        {
            int expectedCount = InitialRepositoryCount;

            KEYED_DOMAIN_OBJECT originalObject = CreateDomainObject();
            KEYED_DOMAIN_OBJECT reloadedObject = default(KEYED_DOMAIN_OBJECT);

            RepositoryToTest.Add(originalObject);
            expectedCount++;

            Assert.AreEqual(expectedCount, RepositoryToTest.Count());
            Assert.AreEqual(expectedCount, RepositoryToTest.FindKeys().Count);
            Assert.AreEqual(expectedCount, RepositoryToTest.Read().Count);
            Assert.AreEqual(expectedCount, RepositoryToTest.Read().Count);
            Assert.IsTrue(RepositoryToTest.Exists(originalObject.Key));
            Assert.IsTrue(RepositoryToTest.FindKeys().Contains(originalObject.Key));

            SaveAndReloadDomainModel();
            reloadedObject = RepositoryToTest.Read(originalObject.Key);

            Assert.AreEqual(expectedCount, RepositoryToTest.Count());
            Assert.AreEqual(expectedCount, RepositoryToTest.FindKeys().Count);
            Assert.AreEqual(expectedCount, RepositoryToTest.Read().Count);
            Assert.AreEqual(expectedCount, RepositoryToTest.Read().Count);
            Assert.IsTrue(RepositoryToTest.Exists(originalObject.Key));
            Assert.IsTrue(RepositoryToTest.FindKeys().Contains(originalObject.Key));

            Assert.AreNotSame(originalObject, reloadedObject);
            Assert.AreEqual(originalObject, reloadedObject);
        }

        [Test]
        public virtual void VerifyRepositoryAfterMultipleAdds()
        {
            int expectedCount = InitialRepositoryCount;

            Dictionary<int, KEYED_DOMAIN_OBJECT> originals = new Dictionary<int, KEYED_DOMAIN_OBJECT>();

            for (int count = 0; count < NumberOfTestInstances; count++)
            {
                KEYED_DOMAIN_OBJECT domainObject = CreateDomainObject();
                originals.Add(count, domainObject);

                RepositoryToTest.Add(domainObject);
                domainObject = RepositoryToTest.Read(domainObject.Key);
                expectedCount++;

                Assert.AreEqual(expectedCount, RepositoryToTest.Count());
                Assert.AreEqual(expectedCount, RepositoryToTest.FindKeys().Count);
                Assert.AreEqual(expectedCount, RepositoryToTest.Read().Count);
                Assert.IsTrue(RepositoryToTest.Exists(domainObject.Key));
                Assert.IsTrue(RepositoryToTest.FindKeys().Contains(domainObject.Key));
            }

            SaveAndReloadDomainModel();

            Assert.AreEqual(expectedCount, RepositoryToTest.Count());
            Assert.AreEqual(expectedCount, RepositoryToTest.FindKeys().Count);
            Assert.AreEqual(expectedCount, RepositoryToTest.Read().Count);

            foreach (int index in originals.Keys)
            {
                KEYED_DOMAIN_OBJECT originalObject = originals[index];
                KEYED_DOMAIN_OBJECT reloadedObject = RepositoryToTest.Read(originalObject.Key);

                Assert.AreNotSame(originalObject, reloadedObject);
                Assert.AreEqual(originalObject, reloadedObject);
            }
        }

        [Test]
        public virtual void VerifyRepositoryAfterSingleUpdate()
        {
            int expectedCount = InitialRepositoryCount;

            bool objectCanBeUpdated = true;
            KEYED_DOMAIN_OBJECT originalObject = CreateDomainObject();
            KEYED_DOMAIN_OBJECT copiedObject = default(KEYED_DOMAIN_OBJECT);
            KEYED_DOMAIN_OBJECT reloadedObject = default(KEYED_DOMAIN_OBJECT);

            RepositoryToTest.Add(originalObject);
            expectedCount++;

            originalObject = RepositoryToTest.Read(originalObject.Key);
            copiedObject = originalObject.Copy();
            UpdateDomainObject(originalObject, ref objectCanBeUpdated);
            Assert.AreNotSame(originalObject, copiedObject);
            if (objectCanBeUpdated)
            { Assert.AreNotEqual(originalObject, copiedObject); }
            else
            { Assert.AreEqual(originalObject, copiedObject); }

            copiedObject = originalObject.Copy();
            RepositoryToTest.Update(originalObject);
            originalObject = RepositoryToTest.Read(originalObject.Key);
            Assert.AreNotSame(originalObject, copiedObject);
            Assert.AreEqual(originalObject, copiedObject);

            Assert.AreEqual(expectedCount, RepositoryToTest.Count());
            Assert.AreEqual(expectedCount, RepositoryToTest.FindKeys().Count);
            Assert.AreEqual(expectedCount, RepositoryToTest.Read().Count);
            Assert.IsTrue(RepositoryToTest.Exists(originalObject.Key));
            Assert.IsTrue(RepositoryToTest.FindKeys().Contains(originalObject.Key));

            SaveAndReloadDomainModel();
            reloadedObject = RepositoryToTest.Read(originalObject.Key);

            Assert.AreEqual(expectedCount, RepositoryToTest.Count());
            Assert.AreEqual(expectedCount, RepositoryToTest.FindKeys().Count);
            Assert.AreEqual(expectedCount, RepositoryToTest.Read().Count);
            Assert.IsTrue(RepositoryToTest.Exists(originalObject.Key));
            Assert.IsTrue(RepositoryToTest.FindKeys().Contains(originalObject.Key));

            Assert.AreNotSame(originalObject, reloadedObject);
            Assert.AreEqual(originalObject, reloadedObject);
        }

        [Test]
        public virtual void VerifyRepositoryAfterSingleRemove()
        {
            int expectedCount = InitialRepositoryCount;

            KEYED_DOMAIN_OBJECT originalObject = CreateDomainObject();

            RepositoryToTest.Add(originalObject);
            expectedCount++;

            RepositoryToTest.Remove(originalObject.Key);
            expectedCount--;

            Assert.AreEqual(expectedCount, RepositoryToTest.Count());
            Assert.AreEqual(expectedCount, RepositoryToTest.FindKeys().Count);
            Assert.AreEqual(expectedCount, RepositoryToTest.Read().Count);
            Assert.IsFalse(RepositoryToTest.Exists(originalObject.Key));
            Assert.IsFalse(RepositoryToTest.FindKeys().Contains(originalObject.Key));

            SaveAndReloadDomainModel();

            Assert.AreEqual(expectedCount, RepositoryToTest.Count());
            Assert.AreEqual(expectedCount, RepositoryToTest.FindKeys().Count);
            Assert.AreEqual(expectedCount, RepositoryToTest.Read().Count);
            Assert.IsFalse(RepositoryToTest.Exists(originalObject.Key));
            Assert.IsFalse(RepositoryToTest.FindKeys().Contains(originalObject.Key));
        }

        [Test]
        public virtual void VerifyRepositoryQueryResults()
        {
            int expectedCount = InitialRepositoryCount;

            KEYED_DOMAIN_OBJECT lastObjectAdded = default(KEYED_DOMAIN_OBJECT);
            for (int count = 0; count < NumberOfTestInstances; count++)
            {
                lastObjectAdded = CreateDomainObject();
                RepositoryToTest.Add(lastObjectAdded);
                expectedCount++;
            }

            Assert.AreEqual(expectedCount, RepositoryToTest.Count());

            string keyFieldName = ClassReflector.GetPropertyName((KEYED_DOMAIN_OBJECT domainObject) => domainObject.Key);
            EqualsQuery<KEYED_DOMAIN_OBJECT> keyQuery = new EqualsQuery<KEYED_DOMAIN_OBJECT>(keyFieldName, lastObjectAdded.Key);
            ICollection<KEYED_DOMAIN_OBJECT> foundObjects = RepositoryToTest.Read(keyQuery);

            Assert.AreEqual(1, foundObjects.Count);
            Assert.AreEqual(lastObjectAdded, foundObjects.First<KEYED_DOMAIN_OBJECT>());
        }
    }
}